#pragma once

#include <google/protobuf/service.h>
#include "tinyrpc/base/task.h"
#include "tinyrpc/net/tcpserver.h"
#include "redis/redisclient.h"
#include "tinyrpc/base/conf/config.h"
#include <functional>
#include <unordered_map>
#include <vector>
namespace TinyRPC{
class TinyRPCClient : public google::protobuf::RpcChannel, public TcpServer{

public:
    TinyRPCClient():TcpServer(nullptr, 1){
        start();
    }

    void init(std::string filePath){
        m_config.init(filePath);
        std::string redis_ip = m_config.LookUp<std::string>("redis.ip");
        int redis_port = m_config.LookUp<int>("redis.port");
            m_redisClient.init(redis_ip, redis_port);
    }
    // 所有通过stub 代理对象调用rpc的方法， 都走到这里了，统一做rpc方法调用的数据序列化
    virtual void CallMethod(const google::protobuf::MethodDescriptor* method,
                          google::protobuf::RpcController* controller, const google::protobuf::Message* request,
                          google::protobuf::Message* response, google::protobuf::Closure* done);
    
    //client.call(std::function<>EchoServiceRpc_Stub::echo, &stub, std::placeholders::_1, std::placeholders::_2,std::placeholders::_3, std::placeholders::_4), request);
    template<typename ParamType, typename ReturnType>
    Future<ReturnType> async_call(std::function<void (ParamType*,ReturnType*)> func, ParamType& param){
        Future<ReturnType> future;
        auto async_func = [&]()->ReturnType{
            func(&param, &future.getUnsafe());
            return future.getUnsafe();
        };
        Task task = Task::AsyncTask<ReturnType>(async_func,future);
        getNextLoop()->runInLoop(task);
        return future;
    }


    template<typename ParamType, typename ReturnType>
    ReturnType call(std::function<void(ParamType*, ReturnType*)> func, ParamType& param){
        ReturnType result;
        func(&param, & result);
        return result;
    }

    template<typename ParamType, typename ReturnType>
    ReturnType call(std::string service_name, std::string method_name, ParamType& param);

    template<typename ParamType, typename ReturnType>
    Future<ReturnType> async_call(std::string service_name, std::string method_name, ParamType& param);


public:
    void registServiceStub(::google::protobuf::Service* service);

    void initRedis();
    void keyEventsubscribe();


private:
    struct ServiceInfo{
        google::protobuf::Service* m_service;
        std::unordered_map<std::string, const google::protobuf::MethodDescriptor*> m_methodMap;
        std::vector<INetAddress> m_servers;
    };

    std::unordered_map<std::string, ServiceInfo> m_serviceMap;

    RedisClient m_redisClient;
    std::vector<std::string> m_subscribeChannels;
    std::unordered_map<std::string, std::vector<std::string>> m_serviceServerMap;
    Config m_config;

};


template<typename ParamType, typename ReturnType>
Future<ReturnType> TinyRPCClient::async_call(std::string service_name, std::string method_name, ParamType& param){
    Future<ReturnType> futureResult;

    auto it_service = m_serviceMap.find(service_name);
    auto it_method = it_service->second.m_methodMap.find(method_name);
    
    google::protobuf::Service * service = it_service->second.m_service;
    const google::protobuf::MethodDescriptor * method = it_method->second;

    auto func = [&]()-> ReturnType{
        service->CallMethod(method, nullptr, &param, &futureResult.getUnsafe(), nullptr);
        //futureResult.setDone();
        return futureResult.getUnsafe();
    };
    
    Task task = Task::AsyncTask<ReturnType>(func, futureResult); 
    // task runinloop
    getNextLoop()->runInLoop(task);
    return futureResult;
}


template<typename ParamType, typename ReturnType>
ReturnType TinyRPCClient::call(std::string service_name, std::string method_name, ParamType& param){
    ReturnType result;

    auto it_service = m_serviceMap.find(service_name);
    auto it_method = it_service->second.m_methodMap.find(method_name);
    
    google::protobuf::Service * service = it_service->second.m_service;
    const google::protobuf::MethodDescriptor * method = it_method->second;

    
    service->CallMethod(method, nullptr, &param, &result, nullptr);
    
    return result;

}

}